﻿using System;
using System.Threading;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.TeamFoundation.Build.Client;
using Microsoft.TeamFoundation.Build.Workflow;
using Microsoft.TeamFoundation.Build.Workflow.Activities;
using System.Diagnostics;
using PowersourceUIToolkit;
using System.ComponentModel.Composition;
using Microsoft.TeamFoundation.Client;
using UCL.Powersource.ServiceReference.RemoteBuildBridge;
using System.ServiceModel.Channels;
using System.ServiceModel;

namespace UCL.Powersource.TFS
{
    internal class RemoteBuilderAdaptor : PropertyChangedInitializedLifeCycle
    {
        //public readonly RemoteBuilderQueue RemoteBuilderQueue;

        internal IBuildDefinition CurrentBuildDefinition;
        internal IQueuedBuild CurrentQueuedBuild;
        internal IBuildRequest CurrentBuildRequest;
        private readonly ILog Log;


        internal static class Conventions
        {
            internal static BuildBridgeConfiguration BridgeConfig = null;
            internal static string BaseBuildName = "PSB";

            internal static string GetDefaultDropLocation(IRemoteBuilderQueueItem buildingItem)
            {
                var timeFrame = buildingItem.TimeFrame;
                var teamProjConfig = buildingItem.Project;
                if (!(timeFrame is ITimeFrameIteration)) return null;

                return BridgeConfig.UNCPathBase + "\\" + teamProjConfig.TeamProjectCollectionConfiguration.Name + "\\" + teamProjConfig.UniqueName;
            }

            internal static string GetBuildRequestDropLocation(IRemoteBuilderQueueItem buildingItem)
            {
                var timeFrame = buildingItem.TimeFrame;
                var teamProjConfig = buildingItem.Project;
                if (!(timeFrame is ITimeFrameIteration)) return null;

                return BridgeConfig.UNCPathBase + "\\" + teamProjConfig.UniqueGuid.ToString();
            }

            internal static string GetBuildName(IRemoteBuilderQueueItem buildingItem)
            {
                var timeFrame = buildingItem.TimeFrame;
                var teamProjConfig = buildingItem.Project;
                if (!(timeFrame is ITimeFrameIteration)) return null;

                return BaseBuildName + "_" + teamProjConfig.UniqueName + "_" + timeFrame.Label.Replace("\\", "_");
            }

            internal static StringList GetProjectsToBuild(IRemoteBuilderQueueItem itemToQuery)
            {
                if (itemToQuery == null) throw new ArgumentNullException("itemToQuery");
                if (itemToQuery.Project == null || itemToQuery.TimeFrame == null)
                {
                    throw new InvalidOperationException(string.Format("Conventions.GetProjectsToBuild cannot be called on this builder queue item. Either Project or Timeframe is null. {0}", itemToQuery.ToString()));
                }
                var timeframepath = itemToQuery.TimeFrame.SourceBranchPath.Replace("\\", "_"); //TODO: SourceBranch patch already follows the conventions, this is leaved here only for compatibility with old tests
                var solutionPath = itemToQuery.Project.SolutionFilePath;
                if (String.IsNullOrWhiteSpace(solutionPath))
                {
                    solutionPath = string.Format("{0}.sln", itemToQuery.Project.UniqueName);
                }
                return new StringList(string.Format("$/{0}/branch/{1}/{2}", itemToQuery.Project.UniqueName, timeframepath, solutionPath));

                //return new StringList(string.Format("$/{0}/branch/{1}/{0}.sln", itemToQuery.Project.UniqueName, itemToQuery.TimeFrame.Label.Replace("\\", "_")));

            }
        }

        internal static class Utilities
        {

            internal static String GetBuildRequestInfo(IBuildRequest br)
            {
                var processParameters = WorkflowHelpers.DeserializeProcessParameters(br.ProcessParameters);
                BuildSettings bs = (BuildSettings)processParameters["BuildSettings"];
                StringList projectsToBuild = new StringList("#NULL");
                if (bs.HasProjectsToBuild) projectsToBuild = bs.ProjectsToBuild;
                return string.Format("[{0}](Source:{1}, Drop:{2}", br.BuildDefinitionUri, projectsToBuild, br.DropLocation);

            }
            internal static IBuildRequest CreateBuildRequest(IBuildDefinition myBuildDef, IRemoteBuilderQueueItem itemToQuery)
            {
                var r = myBuildDef.CreateBuildRequest();

                var processParameters = WorkflowHelpers.DeserializeProcessParameters(r.ProcessParameters);
                var settings = new BuildSettings();
                settings.ProjectsToBuild = Conventions.GetProjectsToBuild(itemToQuery);
                processParameters.Add("BuildSettings", settings);
                r.ProcessParameters = WorkflowHelpers.SerializeProcessParameters(processParameters);
                r.DropLocation = Conventions.GetBuildRequestDropLocation(itemToQuery);
                return r;
            }

            internal static IBuildDefinition GetOrCreateBuildDefiniton(IRemoteBuilderQueueItem buildingItem)
            {
                var timeFrame = buildingItem.TimeFrame;
                var teamProjConfig = buildingItem.Project;
                if (!(timeFrame is ITimeFrameIteration)) return null;

                var tfsColl = IoC.Get<ITfsTeamProjectCollectionFactory>().GetTeamProjectCollection(new Uri(teamProjConfig.TeamProjectCollectionConfiguration.Uri));

                var buildServer = tfsColl.GetService<IBuildServer>();


                var buildControllers = buildServer.QueryBuildControllers(false);

                if (buildControllers.Length < 1) throw new NotSupportedException("There is no Controller in the Build Server. Creation of new controller is not supported. ");

                var buildName = Conventions.GetBuildName(buildingItem);

                IBuildDefinition myBuildDef;
                var bds = buildServer.QueryBuildDefinitions(teamProjConfig.UniqueName);
                myBuildDef = bds.FirstOrDefault(a => a.Name == buildName); //.FirstOrDefault(a=>a.bu
                if (myBuildDef == null)
                {
                    myBuildDef = buildServer.CreateBuildDefinition(teamProjConfig.UniqueName);
                    myBuildDef.BuildController = buildControllers[0];
                    myBuildDef.Name = buildName;
                    myBuildDef.Process = buildServer.QueryProcessTemplates(teamProjConfig.UniqueName).Where(p => p.TemplateType == ProcessTemplateType.Default).First();
                    myBuildDef.DefaultDropLocation = Conventions.GetDefaultDropLocation(buildingItem);


                    var processParameters = WorkflowHelpers.DeserializeProcessParameters(myBuildDef.ProcessParameters);
                    var settings = new BuildSettings();
                    settings.ProjectsToBuild = Conventions.GetProjectsToBuild(buildingItem);
                    processParameters.Add("BuildSettings", settings);
                    processParameters.Add("BuildNumberFormat", "$(Date:yyyyMMdd)$(Rev:.r)");
                    myBuildDef.ProcessParameters = WorkflowHelpers.SerializeProcessParameters(processParameters);
                    myBuildDef.Save();
                }

                return myBuildDef;
            }
        }


        internal RemoteBuilderAdaptor(RemoteBuilderQueue owner)
        {
            Log = LogManager.GetLog(typeof(RemoteBuilderAdaptor));
        }

        public bool CanQueueNewBuild { get { return CurrentBuildDefinition == null && CurrentQueuedBuild == null; } }


        QueuedBuildStatus LastQueuedBuildStatus;
        private Exception TerminateException;

        internal System.Collections.Generic.IEnumerator<QueuedBuildStatus> GetQueuedBuildStatusEnumerator()
        {
            if (CurrentQueuedBuild == null) throw new InvalidOperationException("There is no CurrentQueuedBuild, therefore corouting/enumerating over QueuedBuild statuses is impossible.");
            while (true)
            {
                if (TerminateException == null)
                {
                    Poll();
                }
                else
                {
                    FinishBuildOnAdaptor(CurrentQueuedBuild, TerminateException);
                }
                yield return LastQueuedBuildStatus;
                if (LastQueuedBuildStatus != null && LastQueuedBuildStatus.IsFinalQueuedBuildStatus) break;
                LastQueuedBuildStatus = null;

            }
        }

        internal void QueueNewBuild(IRemoteBuilderQueueItem itemToQuery)
        {
            if (!CanQueueNewBuild) throw new NotSupportedException(string.Format("There is already queued bd in {0}[hc:{1}]. Multiple queued builds are not currently supported.", this.GetType().Name, this.GetHashCode()));
            Debug.Assert(CurrentBuildDefinition == null, "CurrentBuildDefinition != null && CanQueueNewBuild=true");
            Debug.Assert(CurrentQueuedBuild == null, "CurrentQueuedBuild != null && CanQueueNewBuild=true");
            Debug.Assert(CurrentBuildRequest == null, "CurrentBuildRequest != null && CanQueueNewBuild=true");
            TerminateException = null;
            //If there is new queuedbuild LastQueuedBuildStatus=null;
            LastQueuedBuildStatus = null;

            RemoteBuildBridgeClient ibb = null;

            try
            {
                ibb = RemoteBuildBridgeHelper.CreateConnectionToBuildBridgeService(itemToQuery.Project.TeamProjectCollectionConfiguration.WebServiceUrl);
                Conventions.BridgeConfig = ibb.GetConfiguration();
                
            }
            finally
            {
                if (ibb != null) ibb.Close();
            }


            var tfsColl = IoC.Get<ITfsTeamProjectCollectionFactory>().GetTeamProjectCollection(new Uri(itemToQuery.Project.TeamProjectCollectionConfiguration.Uri));

            var buildServer = tfsColl.GetService<IBuildServer>();
            CurrentBuildDefinition = Utilities.GetOrCreateBuildDefiniton(itemToQuery);
            CurrentBuildRequest = Utilities.CreateBuildRequest(CurrentBuildDefinition, itemToQuery);

            Log.Info("Enqueueing bd {0}", Utilities.GetBuildRequestInfo(CurrentBuildRequest));

            CurrentQueuedBuild = buildServer.QueueBuild(CurrentBuildRequest);
            //CurrentQueuedBuild.StatusChanged += CurrentQueuedBuild_StatusChanged;
        }

        private void Poll(IQueuedBuild bd)
        {
            if (LastQueuedBuildStatus != null) throw new InvalidOperationException(String.Format("Error in processing build {1}: LastQueuedBuildStatus {0} was not consumed and polling is recalled. To prevent losing LastQueuedBuildStatus polling must not be performed.", LastQueuedBuildStatus, Utilities.GetBuildRequestInfo(CurrentBuildRequest)));
            Exception PollingException = null;
            if (bd == null) throw new InvalidOperationException("bd == null, unable to perform polling");
            try
            {
                bd.Refresh(QueryOptions.All);
            }
            catch (Exception exc)
            {
                PollingException = exc;
            }
            Log.Trace("Build {0} status changed {1} [ PollingException={2}]", Utilities.GetBuildRequestInfo(CurrentBuildRequest), bd.Status, PollingException);

            //Can there be any call after firs polling exception??
            //Is there any event after bd is done??
            if (PollingException != null)
            {
                Log.Error(PollingException, "Finishing QueuedBuild as there was an exception on Refresh. Disconnecting from build.");
                FinishBuildOnAdaptor(bd, PollingException);
            }
            else
            {
                if (bd.Status == QueueStatus.Completed || bd.Status == QueueStatus.Canceled)
                {
                    FinishBuildOnAdaptor(bd, null);
                }
            }
        }

        public void Poll()
        {
            Poll(CurrentQueuedBuild);
        }

        public void RequestTerminate(Exception exc)
        {
            TerminateException = exc;
            if (CurrentQueuedBuild != null && CurrentQueuedBuild.Build.Status == BuildStatus.InProgress)
            {
                try
                {
                    CurrentQueuedBuild.Cancel();
                }
                catch (Exception cancelExc)
                {
                    var buildId = CurrentQueuedBuild.ToString();
                    if (CurrentQueuedBuild.Build != null) buildId += " " + CurrentQueuedBuild.Build.TeamProject;
                    var texc = "#NULL";
                    if (TerminateException == null) texc = TerminateException.ToString();
                    Log.Error(cancelExc, "RequestTerminate failed- {0}\r\nReason to terminate: {1}", CurrentQueuedBuild.Build.TeamProject, texc);
                }
            }
        }

        /////// <summary>
        /////// This is special handler which warns if there are any events on the QueuedBuild after we consider that bd to be done
        /////// </summary>
        /////// <param name="sender"></param>
        /////// <param name="e"></param>
        ////void NullListener_CurrentQueuedBuild_StatusChanged(object sender, StatusChangedEventArgs e)
        ////{
        ////    Log.DebugMethod("Event after bd finished invoked");
        ////    IQueuedBuild bd = (IQueuedBuild)sender;
        ////    if (e.PollingException != null) Log.Warn(e.PollingException, "There was a Polling exception on IQueuedBuild {0} after Powersource considered bd to be done. Exception ignored.", bd.BuildDefinition.Name);
        ////    if (e.Changed) Log.Warn("There was a StatusChanged on IQueuedBuild {0} after Powersource considered QueuedBuild to be done. Change in status ignored [ItemStatus {1}]", bd.BuildDefinition.Name, bd.ItemStatus);
        ////}

        private void FinishBuildOnAdaptor(IQueuedBuild bd, Exception buildException)
        {
            if (LastQueuedBuildStatus != null)
                Log.Warn("FinishBuildOnAdaptor called, but no one consumed the previous one. LastQueuedBuildStatus {0} will be ommited.", LastQueuedBuildStatus.ToString());

            LastQueuedBuildStatus = new QueuedBuildStatus(buildException, bd.Build, true);
            CurrentBuildDefinition = null;
            CurrentQueuedBuild = null;
            CurrentBuildRequest = null;

        }
        //internal static void RequestAndWaitForRemoteBuild(IRemoteBuilderQueueItem buildingItem)
        //{
        //    if (buildingItem.ItemStatus != QueuedItemStatus.Activated) throw new InvalidOperationException(string.Format("RequestAndWaitForRemoteBuild called for not activated item {0}. Internal error. Cannot perform bd.", buildingItem.ToString()));
        //    //RequestAndWaitForRemoteBuild(buildingItem.Project, buildingItem.TimeFrame);
        //}



        protected override void OnDisposing()
        {
            RequestTerminate(new OperationCanceledException("Disposing build adaptor termintated all builds"));
            return;
        }
        protected override bool OnInitializing()
        {
            return true;
        }



    }

}
